package ru.myrtle.jst.ast;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import ru.myrtle.jst.Problem;
import ru.myrtle.jst.store.Template;

public class Unit {

    private final Template template;

    private String outputPackage;
    private String outputClassName;
    private String outputClassExtends;

    private final Set<String> imports =
            new LinkedHashSet<String>();

    private final Set<String> outputClassImplements =
            new HashSet<String>();

    private final List<Parameter> parameters =
            new ArrayList<Parameter>();

    private final List<String> exceptions =
            new ArrayList<String>();

    private final List<ConstructorParameter> constructorParameters =
            new ArrayList<ConstructorParameter>();

    private final List<Element> elements =
            new ArrayList<Element>();

    private final List<Problem> problems =
            new ArrayList<Problem>();

    /*
     *
     */

    public Unit(Template template) {
        this.template = template;
    }

    /*
     *
     */

    public Template getTemplate() {
        return template;
    }

    /*
     *
     */

    /**
     * Return the name of the Java package to which the compilation unit will be
     * compiled.
     *
     * @return a output package
     */
    public String getOutputPackage() {
        return outputPackage;
    }

    /**
     * Set the Java package into which the compilation unit will be compiled.
     *
     * @param outputPackage The outputPackage to set.
     */
    public void setOutputPackage(String outputPackage) {
        this.outputPackage = outputPackage;
    }

    /*
     *
     */

    public Set<String> getImports() {
        return Collections.unmodifiableSet(imports);
    }

    public void addImports(String... list) {
        imports.addAll(
                Arrays.asList(list)
        );
    }

    /*
     *
     */

    /**
     * Return the unqualified name of the Java class into which the compilation
     * unit will be compiled.
     *
     * @return Returns the outputJavaClassName.
     */
    public String getOutputClassName() {
        return outputClassName;
    }

    /**
     * Set the unqualifeid name of the Java class into which the compilation
     * unit will be compiled.
     *
     * @param outputClassName The outputClassName to set.
     */
    public void setOutputClassName(String outputClassName) {
        this.outputClassName = outputClassName;
    }

    /*
     *
     */

    public String getOutputClassExtends() {
        return outputClassExtends;
    }

    public void setOutputClassExtends(String outputClassExtends) {
        this.outputClassExtends = outputClassExtends;
    }

    /*
     *
     */

    public Set<String> getOutputClassImplements() {
        return Collections.unmodifiableSet(outputClassImplements);
    }

    public void addOutputClassImplements(String... list) {
        outputClassImplements.addAll(
                Arrays.asList(list)
        );
    }

    /*
     *
     */

    public void addParameters(Parameter... parameters) {
        this.parameters.addAll(
                Arrays.asList(parameters)
        );
    }

    public List<Parameter> getParameters() {
        return Collections.unmodifiableList(parameters);
    }

    /*
     *
     */

    public void addExceptions(String... exceptions) {
        this.exceptions.addAll(
                Arrays.asList(exceptions)
        );
    }

    public List<String> getExceptions() {
        return Collections.unmodifiableList(exceptions);
    }

    /*
     *
     */

    public void addConstructorParameters(ConstructorParameter... parameters) {
        this.constructorParameters.addAll(
                Arrays.asList(parameters)
        );
    }

    public List<ConstructorParameter> getConstructorParameters() {
        return constructorParameters;
    }

    /*
     *
     */

    public void addElements(Element... list) {
        elements.addAll(
                Arrays.asList(list)
        );
    }

    /*
     *
     */

    /**
     * Return a list of problems discovered in the compilation unit
     *
     * @return a List of {@link ru.myrtle.jst.Problem} objects. The empty list is returned if
     *         no problems were found.
     */
    public List<Problem> getProblems() {
        return Collections.unmodifiableList(problems);
    }

    boolean hasProblemsOf(Problem.Severity severity) {
        for (Problem problem : problems) {
            if (problem.getSeverity() == severity) {
                return true;
            }
        }

        return false;
    }

    /**
     * Test if the compilation unit has any errors
     *
     * @return <code>true</code> if the compilation unit had errors,
     *         <code>false</code> otherwise.
     */
    public boolean hasErrors() {
        return hasProblemsOf(Problem.Severity.ERROR);
    }

    /**
     * Test if the compilation unit has any warnings
     *
     * @return <code>true</code> if the compilation unit had warnings,
     *         <code>false</code> otherwise.
     */
    public boolean hasWarnings() {
        return hasProblemsOf(Problem.Severity.WARNING);
    }

    /**
     * Create a new problem on the compilation unit
     *
     * @param problem the problem
     */
    public void addProblem(Problem problem) {
        problems.add(problem);
    }

    /*
     *
     */

    public <ContextType> void accept(Visitor<ContextType> visitor, ContextType context) {
        boolean visitChildren = visitor.visit(this, context);

        if (visitChildren) {
            for (Element element : elements) {
                element.accept(visitor, context);
            }
        }
    }
}
